#include "model/sphere.h"

#include "view/widget.h"
#include "view/mainwindow.h"

#include "method/projection.h"
#include "method/shading.h"

#include <QPaintEvent>
#include <QPainter>
#include <QKeyEvent>
#include <QDebug>
#include <math.h>

#define PI 3.141592654

Widget::Widget(MainWindow *parent): QWidget(parent),
    increment(5),
    scene(parent->getScene()),
    projection(nullptr),
    shading(nullptr){

    connect(scene, SIGNAL(changed()), this, SLOT(onSceneChanged()));
    this->resetView();
}

Projection *Widget::getProjection() {
    return this->projection;
}

void Widget::setProjection(Projection *projection) {
    this->projection = projection;
    this->update();
}

Filler *Widget::getShading(){
    return this->shading;
}

void Widget::setShading(Filler *shading){
    this->shading = shading;
    this->update();
}

void Widget::keyPressEvent(QKeyEvent *e) {
    switch(e->key()) {
    case Qt::Key_A:             // camera e ponto focal para esquerda
        this->moveFocus(-increment, 0, 0);
        this->moveObserver(-increment, 0, 0);
        break;
    case Qt::Key_S:             // camera e ponto focal para direita
        this->moveFocus(increment, 0, 0);
        this->moveObserver(increment, 0, 0);
        break;
    case Qt::Key_Q:             // camera e ponto focal para baixo
        this->moveFocus(0, -increment, 0);
        this->moveObserver(0, -increment, 0);
        break;
    case Qt::Key_W:             // camera e ponto focal para cima
        this->moveFocus(0, increment, 0);
        this->moveObserver(0, increment, 0);
        break;
    case Qt::Key_Z:             // aproxima camera do ponto focal
        this->moveVRP2P(-increment);
        break;
    case Qt::Key_X:             // afasta camera do ponto focal
        this->moveVRP2P(increment);
        break;
    case Qt::Key_E:             // roll para esquerda
        this->moveRoll(increment);
        break;
    case Qt::Key_R:             // roll para direita
        this->moveRoll(-increment);
        break;
    case Qt::Key_D:             // azimuth para esquerda
        this->moveViewAngle(-increment, 0);
        break;
    case Qt::Key_F:             // azimuth para direita
        this->moveViewAngle(increment, 0);
        break;
    case Qt::Key_C:             // elevation para cima
        this->moveViewAngle(0, increment);
        break;
    case Qt::Key_V:             // elevation para baixo
        this->moveViewAngle(0, -increment);
        break;
    case Qt::Key_T:             // ponto focal para esquerda
        this->moveFocus(-increment, 0, 0);
        break;
    case Qt::Key_Y:             // ponto focal para direita
        this->moveFocus(increment, 0, 0);
        break;
    case Qt::Key_G:             // ponto focal para cima
        this->moveFocus(0, increment, 0);
        break;
    case Qt::Key_H:             // ponto focal para baixo
        this->moveFocus(0, -increment, 0);
        break;
    case Qt::Key_B:             // aproximar ponto focal da camera
        this->moveP2VRP(-increment);
        break;
    case Qt::Key_N:             // afastar ponto focal da camera
        this->moveP2VRP(increment);
        break;
    case Qt::Key_8:             // testes
        break;
    case Qt::Key_BracketLeft:
        this->updateSphereSize(-increment);
        break;
    case Qt::Key_BraceRight:
        this->updateSphereSize(increment);
        break;

    case Qt::Key_Enter:
        this->scene->getSphere()->setVisible(!this->scene->getSphere()->isVisible());
        this->update();
        break;
    case Qt::Key_Underscore:    // aproximar plano de projecao da camera
    case Qt::Key_Minus:         // aproximar plano de projecao da camera
        this->moveZoom(((coord)increment/100));
        break;
    case Qt::Key_Plus:          // afastar plano de projecao da camera
    case Qt::Key_Equal:         // afastar plano de projecao da camera
        this->moveZoom(((coord)-increment/100));
        break;
    }
}

void Widget::paintEvent(QPaintEvent *) {
    if ( this->projection && this->shading ) {
        Scene* s = this->projection->apply(this->scene, this->observer, this->focus, this->zoom, this->roll);
        this->shading->draw(this, s);
        delete s;
    }
}

double Widget::toRadians(double angle) {
    return angle* PI / 180.0;
}

void Widget::resetView() {
    Object* ground = scene->getGround();
    focus =    ground->center();
    observer = Vertex(focus.x, 5000, 5100);
    zoom = 0.5;
    update();
}

void Widget::updateSphereSize(const double i) {
    coord j = i > 0 ? 1.1 : 0.9;

    MatrixTransformation m;
    m.scale(j, j, j);

    scene->getSphere()->apply(m);
    update();
}

void Widget::changeSphereVisibility() {
    this->scene->getSphere()->setVisible(!this->scene->getSphere()->isVisible());
    this->update();
}

void Widget::onSceneChanged() {
    this->update();
}

void Widget::moveObserver(const coord x, const coord y, const coord z) {
    this->observer.x += x;
    this->observer.y += y;
    this->observer.z += z;
    this->update();
}

void Widget::moveFocus(const coord x, const coord y, const coord z) {
    this->focus.x += x;
    this->focus.y += y;
    this->focus.z += z;
    this->update();
}

void Widget::moveZoom(const coord z) {
    if ( (this->zoom + z) > 0) {
        this->zoom += z;
        this->update();
    }
}

void Widget::moveRoll(const coord ang){
    this->roll += ang;
    this->update();
}

void Widget::moveVRP2P(const coord m) {
    Vertex dist = focus-observer;

    if (m<0 || (dist).distance() > 100) {       //esta afastando ou distancia eh maior que 100
        dist = dist.unit()*m*10;
        //qDebug() << "\nvrp:" << observer.x << observer.y << observer.z << "\n" << "p:" << focus.x << focus.y << focus.z;

        observer = observer + dist;
        this->update();
    }
}

void Widget::moveP2VRP(const coord m) {
    Vertex dist = observer-focus;
    if (m<0 || (dist).distance() > 100) {       //esta afastando ou distancia eh maior que 100
        dist = dist.unit()*m*10;
        //qDebug() << "\nvrp:" << observer.x << observer.y << observer.z << "\n" << "p:" << focus.x << focus.y << focus.z;

        focus = focus + dist;
        this->update();
    }

}


void Widget::moveViewAngle(const int x, const int y) {
    MatrixTransformation m;
    m.translation(-focus.x, -focus.y, -focus.z);
    m.rotationX(x);
    m.rotationY(y);
    m.translation(focus.x, focus.y, focus.z);

    observer.apply(m);
    this->update();
}






